#!/bin/bash
#
# 目的: 安装 Elasticsearch 6.8.22 (单节点或集群)
# 日期: 2024-08-14
# 作者: fanyongfeng

decompression_dir=/data
pkg_install_dir=/data/elasticsearch
pkg_name=elasticsearch-7.17.17
user_name=es
service_name=elasticsearch
HOST_IP=$(ip a | grep global | head -1 | awk -F'/' '{print $1}' | awk '{print $2}')
HOST_NAME=$(echo $HOST_IP | awk -F '.' '{print $NF}')
discovery_hosts=""

[[ -z ${es_password} ]] && es_password="$(gpg --gen-random --armor 1 12)"

service_port=(
    9200
    9300
)

# 检查端口是否被占用
for port in "${service_port[@]}"; do
    portNum=$(ss -ntpl | grep $port | wc -l)
    if [[ $portNum -gt 0 ]]; then
        echo "TCP 端口 ${port} 已被占用，请尝试其他端口！"
        exit 1
    fi
done

# 检查安装包是否存在
if [[ ! -f "${pkg_name}.tar.gz" ]]; then
    echo "${pkg_name}.tar.gz 安装包不存在"
    exit 1
fi

# 创建解压目录
if [ ! -d "${decompression_dir}" ]; then
    mkdir -p ${decompression_dir}
fi

# 安装 JDK
install_jdk() {
    printf "\n<<<<<< 安装 JDK \n"
    if type -p java >/dev/null 2>&1; then
        echo "Java 已安装，版本信息如下："
        java -version
    else
        if [[ ! -f "jdk-8u301-linux-x64.tar.gz" ]]; then
            echo "jdk-8u301-linux-x64.tar.gz 安装包不存在"
            exit 1
        fi
        tar -xzf jdk-8u301-linux-x64.tar.gz -C ${decompression_dir}
        echo "export JAVA_HOME=${decompression_dir}/jdk1.8.0_301
              export JRE_HOME=${decompression_dir}/jdk1.8.0_301/jre
              export CLASSPATH=.:\$JAVA_HOME/lib:\$JRE_HOME/lib:\$CLASSPATH
              export PATH=\$JAVA_HOME/bin:\$JRE_HOME/bin:\$PATH" >> /etc/profile.d/jdk.sh
        source /etc/profile.d/jdk.sh
        java -version
        if [ $? -ne 0 ]; then
            echo "安装 Java 失败"
            exit 1
        fi
    fi
}

# 安装 Elasticsearch
install_elasticsearch() {
    tar -xzf ${pkg_name}.tar.gz -C ${decompression_dir}
    mv ${decompression_dir}/${pkg_name} ${pkg_install_dir}
    mkdir ${pkg_install_dir}/data

    if ! id -u ${user_name} >/dev/null 2>&1; then 
        useradd -M ${user_name} -s /sbin/nologin
    fi
    chown -R ${user_name}:${user_name} ${pkg_install_dir}

    cp ${pkg_install_dir}/config/elasticsearch.yml ${pkg_install_dir}/config/elasticsearch_default.yml
}

# 配置 Elasticsearch
configure_elasticsearch() {
    local is_cluster=$1
    local config_file="${pkg_install_dir}/config/elasticsearch.yml"

cat << EOF > ${config_file}
path.data: ${pkg_install_dir}/data
path.logs: ${pkg_install_dir}/logs
bootstrap.memory_lock: true
network.host: ${HOST_IP}
http.port: 9200
transport.tcp.port: 9300
indices.fielddata.cache.size: 40%

# 安全设置
xpack.security.enabled: true
xpack.license.self_generated.type: basic
EOF

    if [ "$is_cluster" = true ]; then
        echo "cluster.name: my-es-cluster" >> ${config_file}
        echo "node.name: node-${HOST_NAME}" >> ${config_file}
        echo "discovery.zen.ping.unicast.hosts: [${discovery_hosts}:9200]" >> ${config_file}
        echo "discovery.zen.minimum_master_nodes: 2" >> ${config_file}
        echo "xpack.security.transport.ssl.enabled: true" >> ${config_file}
        echo "xpack.security.transport.ssl.verification_mode: certificate" >> ${config_file}
        echo "xpack.security.transport.ssl.key: cert/es/es.key" >> ${config_file}
        echo "xpack.security.transport.ssl.certificate: cert/es/es.crt" >> ${config_file}
        echo "xpack.security.transport.ssl.certificate_authorities: cert/ca/ca.crt" >> ${config_file}
    else
        echo "discovery.type: single-node" >> ${config_file}
    fi
}

# 系统配置
system_config() {
    echo "ELASTICSEARCH=${pkg_install_dir}
PATH=\$PATH:\$ELASTICSEARCH/bin
export ELASTICSEARCH PATH" > /etc/profile.d/es.sh
    source /etc/profile.d/es.sh

    printf "\n<<<<<< 修改内核参数 \n"
    cat << EOF >> /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
* soft memlock unlimited
* hard memlock unlimited
EOF

    cat << EOF >> /etc/sysctl.conf
vm.max_map_count=262144
EOF

    sysctl -p &> /dev/null
}

# 创建服务
create_service() {
    cat << EOF > /usr/lib/systemd/system/$service_name.service
[Unit]
Description=$service_name
Requires=network.service
After=network.service

[Service]
User=$user_name
Group=$user_name
LimitNOFILE=65536
LimitMEMLOCK=infinity
Environment=JAVA_HOME=${decompression_dir}/jdk1.8.0_301
ExecStart=${pkg_install_dir}/bin/elasticsearch
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=mixed
SuccessExitStatus=143
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target
EOF

    systemctl daemon-reload
    systemctl enable ${service_name}
}

# JVM 调优
optimize_jvm() {
    local available_memory=$(free -m | sed -n '2p'| awk '{print $2-$3}')
    if [ ${available_memory} -lt 1024 ]; then
        echo "<<<<<< 机器内存不足以安装 Elasticsearch！至少需要 1GB"
        exit 1
    fi

    local jvm_memory=$((available_memory / 1024 / 2))
    sed -i "s/-Xms1g/-Xms${jvm_memory}g/g" ${pkg_install_dir}/config/jvm.options
    sed -i "s/-Xmx1g/-Xmx${jvm_memory}g/g" ${pkg_install_dir}/config/jvm.options
}

# 创建证书（仅集群模式）
create_certificates() {
    mkdir -p ${pkg_install_dir}/config/cert && cd ${pkg_install_dir}/config/cert

    ${pkg_install_dir}/bin/elasticsearch-certutil ca --days 36500 --keysize 4096 --out elastic-ca.zip --pem
    unzip elastic-ca.zip

    ${pkg_install_dir}/bin/elasticsearch-certutil cert --ca-cert ./ca/ca.crt --ca-key ./ca/ca.key --days 36500 --keysize 4096 --name es --out es-certificate.zip --pem
    unzip es-certificate.zip

    chown -R ${user_name}:${user_name} ${pkg_install_dir}/config/cert
}

# 检查 Elasticsearch 服务状态
check_elasticsearch_status() {
    local max_attempts=30
    local attempt=1
    local delay=10

    echo "正在等待 Elasticsearch 服务启动..."

    while [ $attempt -le $max_attempts ]; do
        if curl -s "${HOST_IP}:${service_port[0]}" >/dev/null; then
            echo "Elasticsearch 服务已启动"
            return 0
        fi

        echo "尝试 $attempt/$max_attempts: Elasticsearch 服务尚未就绪，等待 ${delay} 秒..."
        sleep $delay
        attempt=$((attempt + 1))
    done

    echo "ERROR: Elasticsearch 服务未能在预期时间内启动"
    return 1
}

# 设置密码并开启慢查询日志
set_passwords_and_enable_slowlog() {
    local is_cluster=$1

    systemctl start ${service_name}
    
    if ! check_elasticsearch_status; then
        echo "无法设置密码和开启慢查询日志，因为 Elasticsearch 服务未启动"
        exit 1
    fi

    ${pkg_install_dir}/bin/elasticsearch-setup-passwords interactive << EOF
$es_password
$es_password
$es_password
$es_password
$es_password
$es_password
EOF

    echo "Elasticsearch 密码已设置。所有账户的密码为: $es_password"

    cat << EOF
# 注意：5.x版本以上index级别的设置只能通过api接口动态设置
# 开启慢日志记录
#超过N秒的query产生1个warn日志
#index.search.slowlog.threshold.query.warn: 3s
#超过N秒的fetch产生1个warn日志
#index.search.slowlog.threshold.fetch.warn: 3s
#索引数据超过N秒产生一个warn日志
#index.indexing.slowlog.threshold.index.warn: 3s
# 记录info及以上级别的日志
#index.indexing.slowlog.level: info
EOF
}

# 主函数
main() {
    local is_cluster
    read -p "是否安装集群模式？(y/n): " cluster_choice
    if [[ $cluster_choice == "y" || $cluster_choice == "Y" ]]; then
        is_cluster=true
        read -p "请输入第一个节点IP和端口 (格式: IP:9300): " node1
        read -p "请输入第二个节点IP和端口 (格式: IP:9300): " node2
        read -p "请输入第三个节点IP和端口 (格式: IP:9300): " node3
        discovery_hosts="\"$node1\", \"$node2\", \"$node3\""
    else
        is_cluster=false
    fi

    install_jdk
    install_elasticsearch
    configure_elasticsearch $is_cluster
    system_config
    create_service
    optimize_jvm

    if [ "$is_cluster" = true ]; then
        create_certificates
    fi

    set_passwords_and_enable_slowlog $is_cluster

    printf "\n<<<<<< $service_name 状态 \n"
    systemctl status $service_name.service --no-pager

    echo "Elasticsearch 安装完成。"
}

main